#include <iostream>
#include <stdio.h>
//#include "config.h"

#include "SysPlusPlus/syscall.h"
#include "SysPlusPlus/syslib.h"
#include "SysPlusPlus/Tools.h"
#include "ComPlusPlus/Poll.h"
#include "ComPlusPlus/SocketTcp.h"
#include "SysPlusPlus/GenCfg.h"
#include "SysPlusPlus/ComException.h"

/**
 * Internal socket creation on OS level
 **/
void compp::SocketTcp::NewTcpSocket() {

    this->fd = syspp::Call::Socket ( PF_INET, SOCK_STREAM, 0 );
    if ( this->fd == -1 ) {
      throw syspp::ComException("Panic: Cannot create socket!");
    }
    IsOpen=false;
    FdClosed=false;	

    Async          = false;
    AsyncConnect   = false;
    OptTcpNoDelay  = false;
    Backlog        = 5 ;

}

/**
 *  Destructor. Shuts down the socket, if the socket was attached
 * with the AttachFD Method.
 */
compp::SocketTcp::~SocketTcp() {

  if ( this->fd == -1 )
  	return;	
  if ( this->FD_IsAttached() )
      this->Shutdown ();

}


/**
 *  Constructor.
 * Defaults are applied according to compp::GetComm + set read ahead buffer size 
 */
compp::SocketTcp::SocketTcp( unsigned int buffsz ) : Socket ( buffsz ) {

  NewTcpSocket();
  compp::GenCfg * Cfg = (compp::GenCfg*) compp::GenCfg::Instance();
  SetOptTcpNoDelay( Cfg->GetCommOptTcpNoDelay()) ;
  SetBacklog(Cfg->GetCommBacklog()) ;
  
}


/**
 *  Creates a SocketTcp Class from an already established File Descriptor.
 * 
 * */
compp::SocketTcp::SocketTcp ( struct sockaddr_in *saddr, int len, int fd, unsigned int readaheadbufsz ) : Socket (readaheadbufsz) {

  compp::GenCfg * Cfg = (compp::GenCfg*) compp::GenCfg::Instance(); 
  SetOptTcpNoDelay( Cfg->GetCommOptTcpNoDelay()) ;
  SetBacklog(Cfg->GetCommBacklog()) ;

  this->fd = fd;
  this->IsOpen=true;	
  this->FdClosed=false;

  std::string s;
  s= inet_ntoa ( (const in_addr&) saddr->sin_addr.s_addr) ;
  SocketAddress = s ;

  char ctmp[60];
  unsigned short si;
  si = ntohs( saddr->sin_port);
  sprintf ( ctmp, "%u", si  );

  s+=":";
  s+= ctmp;
  ContextString = s;

  SocketPort = si;
}

/**
  Hard "Close" of the socket. This could lead to data loss.
  !!!!!!!!!!!!!! Handle with care!!!!!!!!!!!!!!!!
*/
void compp::SocketTcp::Close() {

  if ( this->fd == -1 ) {
    return ;
  }
  syspp::Call::Close ( this->fd  );
  this->fd = -1;
  this->IsOpen=false;
  this->FdClosed = true;

}

/**

  Proper protocol shutdown before the socket is closed.
  Use this, if you have sent data and want to make sure,
  the recipient receives the FIN after the last byte read.

*/
void compp::SocketTcp::Shutdown( int method ) {

  syspp::Call::Shutdown ( this->fd, method );    
  Close();

}

/**
 * Establishes the class on host:port and applies 
 * defaults from compp::GenCfg
 * After that carries out bind and listen.
 * Calling this constructor, one creates a working server socket already.
 * Only the Accept method must be used to get a usable clients server connection 
 */
compp::SocketTcp::SocketTcp(const std::string  & Addr, int port) {

  compp::GenCfg * Cfg = (compp::GenCfg*) compp::GenCfg::Instance(); 
  SetOptTcpNoDelay( Cfg->GetCommOptTcpNoDelay()) ;
  SetOptAsyncConnect( Cfg->GetCommOptAsyncConnect()) ;
  SetOptAsync(Cfg->GetCommOptAsync()) ;
  SetBacklog(Cfg->GetCommBacklog()) ;

  NewTcpSocket();
  this->Bind ( Addr, port );
  this->Listen();
}
/**
 * Establishes the class on 0:port and applies 
 * defaults from compp::GenCfg
 * After that carries out bind and listen.
 * Calling this constructor, one creates a working server socket already.
 * Only the Accept method must be used to get a usable clients server connection 
 */

compp::SocketTcp::SocketTcp( int port) {

  compp::GenCfg * Cfg = (compp::GenCfg*) compp::GenCfg::Instance(); 
  SetOptTcpNoDelay( Cfg->GetCommOptTcpNoDelay()) ;
  SetOptAsyncConnect( Cfg->GetCommOptAsyncConnect()) ;
  SetOptAsync(Cfg->GetCommOptAsync()) ;
  SetBacklog(Cfg->GetCommBacklog()) ;

  NewTcpSocket();
  this->Bind ( SocketANYHOST, port );
  this->Listen();
}


/**
 * Setup socket and Listen to it, setting backlog. If backlog is < 0 or
 * backlog > 10000 it is reset to default, which 5.
 */    
void compp::SocketTcp::Listen ( const int backlog ) {

    if ( backlog < 1 || backlog > 10000 ) {
	//syspp::Logger *LogObj = syspp::Logger::Instance( );
	//LogObj->Notice ( "compp::SocketTcp::Listen Resetting backlog to 5.");
	;
    }

  if ( syspp::Call::Listen ( this->fd, backlog ) != 0 ) {
    throw  syspp::ComException (  "compp::SocketTcp::Listen. Listen() failed");
  }
  
}

/**
 * Listen to socket with backlog default set to 5.
 */
void compp::SocketTcp::Listen ( ) {

  //  compp::GenCfg * Cfg = (compp::GenCfg*) compp::GenCfg::Instance(); 

  Listen ( GetBacklog() );
}

/**
 * Binds on string:port
 * 
 */
void compp::SocketTcp::Bind ( const std::string & addr, int port) {
  struct sockaddr_in ad;
 in_addr_t adv4;


  struct hostent *hent;

  (void) memset((void *) &adv4, 0, sizeof (adv4));

  if ( addr == SocketANYHOST ) {
    memset ( &  ad.sin_addr ,0, sizeof (ad.sin_addr));
  } else {
    if ( INADDR_NONE == ( adv4 = inet_addr ( addr.c_str() ))) {
      if ( this->GetUseDNS()  ) {
		if (NULL == (hent = syspp::Lib::Gethostbyname (addr.c_str() ))) {
		  //syspp::Logger *Log = syspp::Logger::Instance();
		  //Log->Error ( "Cannot Resolve Hostname" );
		  throw  syspp::ComException (  "compp::SocketTcp:Bind  Cannot Resolve Hostname " );
		} else {
		  std::memmove( &(adv4), *hent->h_addr_list , sizeof(adv4));
		  ad.sin_addr.s_addr = adv4; 
		}
      }
    } else {
      ad.sin_addr.s_addr = adv4;
    }
  }
  ad.sin_family = AF_INET;
  ad.sin_port   = htons(port);
  
  if ( -1 == syspp::Call::Bind( this->fd, (struct sockaddr* )&ad,  sizeof(ad))) { 
    //syspp::Logger *Log = syspp::Logger::Instance();
    //Log->Error ( "Cannot bind." );
    throw  syspp::ComException (  "compp::SocketTcp::Bind  Cannot Bind " );
  }
  this->SocketPort = port;
  State = Bound;


}

/**
 * Binds on 0:port
 */
void compp::SocketTcp::Bind ( const int port ) {

  this->Bind ( SocketANYHOST, port );

}

/**
 * Connect Socket to addr:port
*/
bool compp::SocketTcp::Connect( const std::string &addr, const int port ){	

  if ( this->fd == -1 )
      NewTcpSocket();

  if (this->GetOptAsyncConnect()) {
    SetOptNonBlocking();
  }
    
  bool retval ;
  if ( false == ( retval = _Connect( addr, port ) ) ) {
    return retval;
  }

  this->SetSockOptTcpNoDelay ( this->GetOptTcpNoDelay() );

  if (this->GetOptSync() ) {
    SetOptNonBlocking();
  }

  IsOpen=true;
  FdClosed=false;
  this->ReadAheadBufferSize      = 0; 
  this->ReadAheadBufferIndex     = 0;

  return retval;
}


/**
 * Enabling/Disabling Nagle's algorithm. This is useful, if you
 * need to send smaller packets to the other end without delay.
 * The tradeoff is, that one gets more packets on the network,
 * so overall throughput of the network is lowered.
 */

bool compp::SocketTcp::SetSockOptTcpNoDelay ( bool yesno ) {
    int opt;

    if ( yesno == true ) 
	opt = 1;
    else
	opt = 0;

    if ( 0 != syspp::Call::Setsockopt (this->fd, IPPROTO_TCP, TCP_NODELAY, (void*)&opt, sizeof(opt) )) {
		return false;
    }
    return true;	
    
}

/**
 * Modifier
 **/
bool compp::SocketTcp::SetSockOptTCP_MAXSEG ( int val ) {	
#ifndef TCP_MAXSEG
  return false;
#else

    int opt =  val;


    if ( 0 != syspp::Call::Setsockopt (this->fd, IPPROTO_TCP, TCP_MAXSEG, (void*)&opt, sizeof(opt) )) {
		return false;
    }
	
	return true;	
#endif

}



/**
 * Accepting the connection. Returns a socket with the connection.
 * Standard settings applied.
 * After accepting the connection, the Accept() - Method creates a 
 * Socket Object with the new connection on the heap and returns
 * a pointer to it. 
*/


compp::SocketTcp* compp::SocketTcp::Accept ( void ) {

  struct sockaddr_in addr_in;

  int l;
  int rets;
  compp::SocketTcp *returnSocket;
 
  this->SetSockOptLinger (this->GetLingerTime());
  l = sizeof ( addr_in );
  if ( (rets = syspp::Call::Accept ( this->fd, (struct sockaddr *) &addr_in, &l )) == -1 ) {
    throw  syspp::ComException (  "compp::SocketTcp::Accept(). Accept() failed.");
  }


  returnSocket = new SocketTcp (  &addr_in, (int)l, rets, ReadAheadBufferTotalSize ) ;
  returnSocket->SetSockOptLinger (this->GetLingerTime());
  returnSocket->SetSockOptTcpNoDelay ( GetOptTcpNoDelay() );
  returnSocket->SetReadAhead ( this->ReadAhead  );
 
  return returnSocket;
}



/**
 * Modifier
 **/
void compp::SocketTcp::SetOptTcpNoDelay( bool val) {
  OptTcpNoDelay = val;
}

bool compp::SocketTcp::GetOptTcpNoDelay() const {
  return OptTcpNoDelay;
}

/**
 * Modifier
 **/
void compp::SocketTcp::SetOptAsyncConnect( bool val) {
  AsyncConnect = val;
}

/**
 * Accessor
 **/
bool compp::SocketTcp::GetOptAsyncConnect() const{
  return AsyncConnect;
}

/**
 * Modifier
 **/
void compp::SocketTcp::SetOptAsync(bool val) {
  Async = val;
}


/**
 * Accessor
 **/
bool compp::SocketTcp::GetOptSync() const{
  return Async;
}

/**
 * Modifier
 **/
void compp::SocketTcp::SetBacklog(int val) {
  Backlog = val;
}

bool compp::SocketTcp::GetBacklog() const{
  return Backlog;
}


const std::string compp::SocketTcp::GetPeerSockIPAddr() const {

 if ( fd == -1 ) {
      throw syspp::ComException("Cannot get peer from not connected socket.");
 }

 struct sockaddr_in saddr;
 socklen_t len = sizeof ( saddr ) ;
 if (  0 != getpeername(fd, (struct sockaddr *) &saddr, &len)) {
      throw syspp::ComException("Cannot get peer address");
 }

 std::string retval ;
 retval = inet_ntoa ( (const in_addr&) saddr.sin_addr.s_addr) ;

 return retval;
}

const unsigned short  compp::SocketTcp::GetPeerSockPort() const {

 if ( fd == -1 ) {
      throw syspp::ComException("Cannot get peer from not connected socket.");
 }

 struct sockaddr_in saddr;
 socklen_t len = sizeof ( saddr ) ;
 if (  0 != getpeername(fd, (struct sockaddr *) &saddr, &len)) {
      throw syspp::ComException("Cannot get peer socket address");
 }

 std::string retval ;
 retval = inet_ntoa ( (const in_addr&) saddr.sin_addr.s_addr) ;
 return ntohs(saddr.sin_port) ;
}


const std::string compp::SocketTcp::GetSockIPAddr() const {

 if ( fd == -1 ) {
      throw syspp::ComException("Cannot get address from not connected socket.");
 }

 struct sockaddr_in saddr;
 socklen_t len = sizeof ( saddr ) ;
 if (  0 != getsockname(fd, (struct sockaddr *) &saddr, &len)) {
      throw syspp::ComException("Cannot get socket address");
 }

 std::string retval ;
 retval = inet_ntoa ( (const in_addr&) saddr.sin_addr.s_addr) ;

 return retval;
}

const unsigned short  compp::SocketTcp::GetSockPort() const {

 if ( fd == -1 ) {
      throw syspp::ComException("Cannot get address from not connected socket.");
 }

 struct sockaddr_in saddr;
 socklen_t len = sizeof ( saddr ) ;
 if (  0 != getsockname(fd, (struct sockaddr *) &saddr, &len)) {
      throw syspp::ComException("Cannot get socket address");
 }

 std::string retval ;
 retval = inet_ntoa ( (const in_addr&) saddr.sin_addr.s_addr) ;
 return ntohs(saddr.sin_port) ;
}



